package com.cyk.like_test.api;

import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.cyk.like_test.model.constants.MqConst;
import com.cyk.like_test.model.entity.AlbumLike;
import com.cyk.like_test.model.entity.AlbumLikeGO;
import com.cyk.like_test.model.entity.AlbumStat;
import com.cyk.like_test.model.entity.AlbumStatGO;
import com.cyk.like_test.model.msg.LikeMsg;
import com.cyk.like_test.model.type.RedisKeyConst;
import com.cyk.like_test.model.vo.ActDTO;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Date;

@RestController
@RequestMapping("/album/cmd")
public class AlbumCmdApi {

    @Resource
    private MongoTemplate mongoTemplate;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private ObjectMapper objectMapper;

    private static final Object locker1 = new Object();
    private static final Object locker2 = new Object();
    private static final Object locker3 = new Object();
    private static final Object locker4 = new Object();
    private static final Object locker5 = new Object();

    @PostMapping("/mysql/one")
    public String MySQLOne(@RequestBody @Valid ActDTO dto) {
        synchronized (locker1) {
            boolean exists = Db.lambdaQuery(AlbumLike.class)
                    .eq(AlbumLike::getPostId, dto.getPostId())
                    .eq(AlbumLike::getTargetId, dto.getTargetId())
                    .exists();
            if(!exists) {
                Db.save(
                        AlbumLike.builder()
                                .postId(Long.valueOf(dto.getPostId()))
                                .targetId(Long.valueOf(dto.getTargetId()))
                                .ctTime(new Date())
                                .utTime(new Date())
                                .build()
                );
            } else {
                Db.lambdaUpdate(AlbumLike.class)
                        .eq(AlbumLike::getPostId, dto.getPostId())
                        .eq(AlbumLike::getTargetId, dto.getTargetId())
                        .remove();
            }
        }
        return "ok";
    }

    @PostMapping("/mysql/two")
    public String MySQLTwo(@RequestBody @Valid ActDTO dto) {
        synchronized (locker2) {
            boolean exists = Db.lambdaQuery(AlbumLike.class)
                    .eq(AlbumLike::getPostId, dto.getPostId())
                    .eq(AlbumLike::getTargetId, dto.getTargetId())
                    .exists();
            if(!exists) {
                Db.save(
                        AlbumLike.builder()
                                .postId(Long.valueOf(dto.getPostId()))
                                .targetId(Long.valueOf(dto.getTargetId()))
                                .ctTime(new Date())
                                .utTime(new Date())
                                .build()
                );
                Db.lambdaUpdate(AlbumStat.class)
                        .setSql("like_cnt = like_cnt + 1")
                        .eq(AlbumStat::getAlbumId, dto.getTargetId())
                        .update();
            } else {
                Db.lambdaUpdate(AlbumLike.class)
                        .eq(AlbumLike::getPostId, dto.getPostId())
                        .eq(AlbumLike::getTargetId, dto.getTargetId())
                        .remove();
                Db.lambdaUpdate(AlbumStat.class)
                        .setSql("like_cnt = like_cnt - 1")
                        .eq(AlbumStat::getAlbumId, dto.getTargetId())
                        .update();
            }
        }
        return "ok";
    }

    @SneakyThrows
    @PostMapping("/mq/one")
    public String MqOne(@RequestBody @Valid ActDTO dto) {
        long postId = Long.parseLong(dto.getPostId());
        long targetId = Long.parseLong(dto.getTargetId());
        synchronized (locker3) {
            boolean exists = Db.lambdaQuery(AlbumLike.class)
                    .eq(AlbumLike::getPostId, postId)
                    .eq(AlbumLike::getTargetId, targetId)
                    .exists();
            if(!exists) {
                Db.save(
                        AlbumLike.builder()
                                .postId(postId)
                                .targetId(targetId)
                                .ctTime(new Date())
                                .utTime(new Date())
                                .build()
                );
            } else {
                Db.lambdaUpdate(AlbumLike.class)
                        .eq(AlbumLike::getPostId, postId)
                        .eq(AlbumLike::getTargetId, targetId)
                        .remove();
            }
            rabbitTemplate.convertAndSend(
                    MqConst.STAT_DIRECT,
                    MqConst.LIKE_MySQL_QUEUE,
                    objectMapper.writeValueAsString(
                            LikeMsg.builder()
                                    .targetId(targetId)
                                    .isLike(!exists)
                                    .build()
                    )
            );
        }
        return "ok";
    }

    @SneakyThrows
    @PostMapping("/redis/one")
    public String redisOne(@RequestBody @Valid ActDTO dto) {
        synchronized (locker4) {
            boolean isExists = Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(
                    RedisKeyConst.ALBUM_LIKE + dto.getTargetId(),
                    dto.getPostId())
            );
            Boolean isLike = null;
            if(isExists) {
                redisTemplate.opsForSet().remove(
                        RedisKeyConst.ALBUM_LIKE + dto.getTargetId(),
                        dto.getPostId()
                );
                isLike = false;
            } else {
                isExists = Db.lambdaQuery(AlbumLike.class)
                        .eq(AlbumLike::getPostId, dto.getPostId())
                        .eq(AlbumLike::getTargetId, dto.getTargetId())
                        .exists();
                if(!isExists) {
                    redisTemplate.opsForSet().add(
                            RedisKeyConst.ALBUM_LIKE + dto.getTargetId(),
                            dto.getPostId()
                    );
                    isLike = true;
                } else {
                    isLike = false;
                }
            }
            rabbitTemplate.convertAndSend(
                    MqConst.STAT_DIRECT,
                    MqConst.SYNC_LIKE_MYSQL_QUEUE,
                    objectMapper.writeValueAsString(
                            LikeMsg.builder()
                                    .isLike(isLike)
                                    .postId(Long.valueOf(dto.getPostId()))
                                    .targetId(Long.valueOf(dto.getTargetId()))
                                    .build()
                    )
            );
        }
        return "ok";
    }

    @SneakyThrows
    @PostMapping("/mongo/one")
    public String mongoOne(@RequestBody @Valid ActDTO dto) {
        long postId = Long.parseLong(dto.getPostId());
        long targetId  = Long.parseLong(dto.getTargetId());
        synchronized (locker5) {
            boolean exists = mongoTemplate.exists(
                    Query.query(
                            Criteria
                                    .where("post_id")
                                    .is(postId)
                                    .and("target_id")
                                    .is(targetId)
                    ),
                    AlbumLikeGO.class
            );
            if(!exists) {
                mongoTemplate.insert(
                        AlbumLikeGO.builder()
                                .postId(postId)
                                .targetId(targetId)
                                .ctTime(new Date())
                                .utTime(new Date())
                                .build()
                );
                mongoTemplate.updateFirst(
                        Query.query(
                                Criteria
                                        .where("_id")
                                        .is(targetId)
                        ),
                        new Update().inc("like_cnt", -1),
                        AlbumStatGO.class
                );
            } else {
                mongoTemplate.remove(
                        Query.query(
                                Criteria
                                        .where("post_id")
                                        .is(postId)
                                        .and("target_id")
                                        .is(targetId)
                        ),
                        AlbumLikeGO.class
                );
                mongoTemplate.updateFirst(
                        Query.query(
                                Criteria
                                        .where("_id")
                                        .is(targetId)
                        ),
                        new Update().inc("like_cnt", -1),
                        AlbumStatGO.class
                );
            }
        }
        return "ok";
    }

}
